#include <stl/set.h>
#include <standard/geometrymath.h>
#include <standard/filewriter.h>
#include <standard/system.h>
#include "terrainobject.h"
#include "terraintexturelist.h"
#include "terrainblock.h"

CTerrainObject::CTerrainObject( CScene* pScene ){
    m_nGameObjectType = 0;
    m_pScene = pScene;
    m_pHeightMap = NULL;
    m_pColorMap = NULL;

    uint16 _nIndex = 0;
    uint16* _pIndex =  m_vTerrainBlockIndexData;
	for ( int32 j = 0; j < SUBBLOCKGRIDCOUNT-1; j++) {
		for (int32 i = 0; i < SUBBLOCKGRIDCOUNT-1; i++) {
            *_pIndex = ( _nIndex + 0 ); _pIndex ++;
            *_pIndex = ( _nIndex + SUBBLOCKGRIDCOUNT + 0 ); _pIndex ++;
            *_pIndex = ( _nIndex + SUBBLOCKGRIDCOUNT + 1 ); _pIndex ++;
            *_pIndex = ( _nIndex + SUBBLOCKGRIDCOUNT + 1 ); _pIndex ++;
            *_pIndex = ( _nIndex + 1 ); _pIndex ++;
            *_pIndex = ( _nIndex + 0 ); _pIndex ++;
            _nIndex ++;
        }
        _nIndex ++;
    }
    m_nTerrainBlockIndexCount = _pIndex - m_vTerrainBlockIndexData;
    m_rTerrainBlockIndexBuffer = NEW CIndexBuffer( m_vTerrainBlockIndexData, m_nTerrainBlockIndexCount, False, sizeof( uint16 ) );
}
CTerrainObject::~CTerrainObject(){
    FREE( m_pHeightMap );
    FREE( m_pColorMap );
    DELETE2DARRAY( m_ppvTerrainBlockList );
}
void CTerrainObject::SetSize( const char* strResourceSpace, int32 nBlockCountX, int32 nBlockCountY, float fBlockSize ){
    m_rTerrainTextureList = NEW CTerrainTextureList();
    m_rTerrainTextureList->Init( strResourceSpace );

    m_nBlockCountX = nBlockCountX;
    m_nBlockCountY = nBlockCountY;
    m_fBlockSize = fBlockSize;

    float _fStartPosX = -(fBlockSize * (m_nBlockCountX) * 0.5f );
    float _fStartPosY = -(fBlockSize * (m_nBlockCountY) * 0.5f );

    m_fStartPosX = _fStartPosX;
    m_fStartPosY = _fStartPosY;

    int32 _nSizeBlockX = nBlockCountX;
    int32 _nSizeBlockY = nBlockCountY;

    m_fCellSize = fBlockSize / (SUBBLOCKGRIDCOUNT - 1);
    m_fHalfCellSize = m_fCellSize * 0.5f;

    m_nCellCountX = (_nSizeBlockX * (SUBBLOCKGRIDCOUNT - 1) + 1);
    m_nCellCountY = (_nSizeBlockY * (SUBBLOCKGRIDCOUNT - 1) + 1);

    m_pHeightMap = (float*)MALLOC( m_nCellCountX * m_nCellCountY * sizeof( float ) );
    memset( m_pHeightMap, 0, m_nCellCountX * m_nCellCountY * sizeof( float ) );

    m_pColorMap =  (color32*)MALLOC( m_nCellCountX * m_nCellCountY * sizeof( color32 ) );
    memset( m_pColorMap, 0xff, m_nCellCountX * m_nCellCountY * sizeof( color32 ) );

    ////m_pHeightMap[0] = 100.0f;
    ////m_pHeightMap[8] = 100.0f;
    ////m_pHeightMap[16] = 100.0f;
    ////m_pHeightMap[8 * 17] = 100.0f;
    ////m_pHeightMap[16*17] = 100.0f;
    ////m_pHeightMap[17*17 - 1] = 100.0f;
    
    //for( int32 i = 0; i < m_nCellCountX * m_nCellCountY; i ++ ){
    //    m_pHeightMap[i] = fRand( -100, 100 );
    //}

    m_rRenderItem = NEW CRenderItem( m_vWorldM );
    m_rRenderItem->m_eRenderType = 0;
    m_rRenderItem->m_bCastShadow = False;
    m_rRenderItem->m_bReceiveShadow = True;
    m_rRenderItem->m_bFogEnable = True;
    m_rRenderItem->m_bLightEnable = True;
    m_rRenderItem->m_bLineMode = False;

    CIndexBuffer* _pTBIB = m_rTerrainBlockIndexBuffer.GetEntityData();

    CRenderElement* _pRenderElement = m_rRenderItem->m_pRenderElement.GetEntityData();
    NEW2DARRAY( m_ppvTerrainBlockList, CTerrainBlock*, _nSizeBlockX, _nSizeBlockY );
    CTexture* _pTextureObj = m_rTerrainTextureList->GetTextureFile( "default.jpg" );
    int32 _nBlockID = 0;
    for (int32 j = 0; j < _nSizeBlockY; j ++) {
        for(int32 i = 0; i < _nSizeBlockX; i++) {
            float _fPosX = _fStartPosX + i * fBlockSize;
            float _fPosY = _fStartPosY + j * fBlockSize;
            CTerrainBlock* _pTerrainBlock = NEW CTerrainBlock( _nBlockID ++, _nSizeBlockX, _nSizeBlockY, i, j, _fPosX, _fPosY, fBlockSize, _pTextureObj  );
            _pTerrainBlock->Init( m_pHeightMap, m_pColorMap, _pRenderElement, _pTBIB );
            m_ppvTerrainBlockList[j][i] = _pTerrainBlock;
            m_vTerrainBlockPool.PushBack(_pTerrainBlock);
        }
    }
}
void CTerrainObject::GetBlockDim( int32 *pBlockSizeX, int32 *pBlockSizeY ){
    *pBlockSizeX = m_nBlockCountX;
    *pBlockSizeY = m_nBlockCountY;
}
CTerrainBlock* CTerrainObject::GetBlock( int32 nCoordX, int32 nCoordY ){
    if ( nCoordX < 0 || nCoordX >= m_nBlockCountX || nCoordY < 0 || nCoordY >= m_nBlockCountY ) {
        return NULL;
    }
    return m_ppvTerrainBlockList[nCoordY][nCoordX];
}
CTerrainBlock* CTerrainObject::SelectBlock( CRaycaster* pRaycaster, Vector3 out_ ){
    for (int32 j = 0; j < m_nBlockCountY; j ++) {
        for(int32 i = 0; i < m_nBlockCountX; i++) {
            if(m_ppvTerrainBlockList[j][i]->Intersect( pRaycaster, m_vWorldM, m_vTerrainBlockIndexData, m_nTerrainBlockIndexCount, out_ ) == True){
                return m_ppvTerrainBlockList[j][i];
            }
        }
    }
    return NULL;
}
bool32 CTerrainObject::GetCellPos( float x, float y, int32* pCellX_, int32* pCellY_ ){
    float _x = x - m_fStartPosX + m_fHalfCellSize;
    float _y = y - m_fStartPosY + m_fHalfCellSize;
    *pCellX_ = _x / m_fCellSize;
    *pCellY_ = _y / m_fCellSize;
    return True;
}
float CTerrainObject::GetCellHeight( int32 nCellX, int32 nCellY ){
    if( nCellX < 0 || nCellY < 0 || nCellX >= m_nCellCountX || nCellY >= m_nCellCountY ) return 0.0f;
    return m_pHeightMap[ nCellY * m_nCellCountX + nCellX];
}
void CTerrainObject::SetCellHeight( int32 nCellX, int32 nCellY, float fHeight ){
    if( nCellX < 0 || nCellY < 0 || nCellX >= m_nCellCountX || nCellY >= m_nCellCountY ) return;
    m_pHeightMap[ nCellY * m_nCellCountX + nCellX] = fHeight;
}

color32 CTerrainObject::GetCellColor( int32 nCellX, int32 nCellY ){
    if( nCellX < 0 || nCellY < 0 || nCellX >= m_nCellCountX || nCellY >= m_nCellCountY ) return 0.0f;
    return m_pColorMap[ nCellY * m_nCellCountX + nCellX];
}
void CTerrainObject::SetCellColor( int32 nCellX, int32 nCellY, color32 nColor ){
    if( nCellX < 0 || nCellY < 0 || nCellX >= m_nCellCountX || nCellY >= m_nCellCountY ) return;
    m_pColorMap[ nCellY * m_nCellCountX + nCellX] = nColor;
}
int32 CTerrainObject::GetCellBlock( int32 nCellX, int32 nCellY, CTerrainBlock* vTerrainBlock[4] ){
    int32 _nBlockGridCount = (SUBBLOCKGRIDCOUNT - 1);
    int32 _nBlockPosX = nCellX / _nBlockGridCount;
    int32 _nBlockPosY = nCellY / _nBlockGridCount;

    int32 _nRet = 0;

    vTerrainBlock[ _nRet ] = GetBlock( _nBlockPosX, _nBlockPosY );
    if( vTerrainBlock[ _nRet ] )_nRet ++;
    if( nCellX % _nBlockGridCount == 0 ){
        vTerrainBlock[ _nRet ] = GetBlock( _nBlockPosX - 1, _nBlockPosY );
        if( vTerrainBlock[ _nRet ] )_nRet ++;
    }
    if( nCellY % _nBlockGridCount == 0 ){
        vTerrainBlock[ _nRet ] = GetBlock( _nBlockPosX, _nBlockPosY - 1 );
        if( vTerrainBlock[ _nRet ] )_nRet ++;
        if( nCellX % _nBlockGridCount == 0 ){
            vTerrainBlock[ _nRet ] = GetBlock( _nBlockPosX - 1, _nBlockPosY - 1 );
            if( vTerrainBlock[ _nRet ] )_nRet ++;
        }
    }
    return _nRet;
   // return GetBlock( _nBlockPosX, _nBlockPosY );
}
int32 CTerrainObject::GetRenderElementHandles( CRenderElement** vRenderElementHandle, int32 nMaxCount ){
    vRenderElementHandle[0] = m_rRenderItem->m_pRenderElement.GetEntityData();
    return 1;
}
float CTerrainObject::GetBlockGeometryHeight( float x, float y ){
    int32 _nCellX, _nCellY;
    GetCellPos( x, y, &_nCellX, &_nCellY );

    float _fCellLeft = _nCellX * m_fCellSize + m_fStartPosX - m_fHalfCellSize;
    float _fCellTop = _nCellY * m_fCellSize + m_fStartPosY - m_fHalfCellSize;
    float _fCellRight = _fCellLeft + m_fCellSize;
    float _fCellBottom = _fCellTop + m_fCellSize;


    TV3 _v0, _v1, _v2, _v3;
    TV3_SETS( _v0, _fCellLeft, GetCellHeight( _nCellX, _nCellY ), _fCellTop );
    TV3_SETS( _v1, _fCellRight, GetCellHeight( _nCellX + 1, _nCellY ), _fCellTop );
    TV3_SETS( _v2, _fCellRight, GetCellHeight( _nCellX + 1, _nCellY + 1 ), _fCellBottom );
    TV3_SETS( _v3, _fCellLeft, GetCellHeight( _nCellX, _nCellY + 1 ), _fCellBottom );
   
	TV2 PT0, PT1, PT2;
	PT0.X = _v0.X; PT0.Y = _v0.Z;
	PT1.X = _v2.X; PT1.Y = _v2.Z;
	PT2.X = x; PT2.Y = y;

	TV3 V0, V1, V2;
	if( GetLineSide( PT0, PT1, PT2 ) > 0 ){
		V0.X = _v0.X;V0.Y = _v0.Z;V0.Z = _v0.Y;
		V1.X = _v1.X;V1.Y = _v1.Z;V1.Z = _v1.Y;
		V2.X = _v2.X;V2.Y = _v2.Z;V2.Z = _v2.Y;
	}else{
		V0.X = _v0.X;V0.Y = _v0.Z;V0.Z = _v0.Y;
		V1.X = _v3.X;V1.Y = _v3.Z;V1.Z = _v3.Y;
		V2.X = _v2.X;V2.Y = _v2.Z;V2.Z = _v2.Y;
	}
	return GetPanelZValue( V0, V1, V2, x, y );
}
bool32 CTerrainObject::SetLayerTexture( int32 nCellX, int32 nCellY, const char* strTextureFile, int32 nBrushType, int32 nBrushSize, int32 nBrushAlpha ){
    //float _fMaxDistance = sqrt(Distance(x, z, x + _nHalfBrushSize, z + _nHalfBrushSize));
    set<CTerrainBlock*> _vTerrainBlockList;
    CTerrainBlock* _vTerrainBlock[4];
    int32 _nHalfBrushSize = nBrushSize / 2;

    CTexture* _pTexture = m_rTerrainTextureList->GetTextureFile( strTextureFile );

    for (int32 j = nCellY - _nHalfBrushSize; j <= nCellY + _nHalfBrushSize; j++) {
        for (int32 i = nCellX - _nHalfBrushSize; i <= nCellX + _nHalfBrushSize; i++) {
            if(i < 0 || j < 0 || i >= this->m_nCellCountX || j >= this->m_nCellCountY)
                continue;

            int32 _nTerrainBlock = this->GetCellBlock( i, j, _vTerrainBlock );
            for( int32 n = 0; n < _nTerrainBlock; n ++ ){
                _vTerrainBlock[n]->SetTextureLayerCoords( i, j, _pTexture, nBrushAlpha );
                _vTerrainBlockList.insert( _vTerrainBlock[n] );
            }
        }
    }

    set<CTerrainBlock*>::iterator it = _vTerrainBlockList.begin();
    set<CTerrainBlock*>::iterator it_end = _vTerrainBlockList.end();
    for( ; it != it_end; it ++ ){
        CTerrainBlock* _pTerrainBlock = it.first();
        _pTerrainBlock->UpdateTextureInfo();
    }

    return True;
}
bool32 CTerrainObject::Save( CFileWriter* pFileWrite ){
	char _strTextBuff[ 1024 ];
	int32 _nTextLen;
	int32 i, j, k;
    _nTextLen = snprintf( _strTextBuff, 1024, "<terrain blockcountx=\"%d\"blockcounty=\"%d\"blocksize=\"%f\">\n",
						  m_nBlockCountX, m_nBlockCountY, m_fBlockSize );
    pFileWrite->AddText( _strTextBuff, _nTextLen );
    //blocklist
    CTerrainTextureList* _pTerrainTextureList = m_rTerrainTextureList.GetEntityData();
    _nTextLen = snprintf( _strTextBuff, 1024, "<blocklist>\n" );
    pFileWrite->AddText( _strTextBuff, _nTextLen );
	for( j = 0; j < m_nBlockCountY; j++ ){
		for( i = 0; i < m_nBlockCountX; i++ ){
			CTerrainBlock* _pBlock = m_ppvTerrainBlockList[j][i];
			_nTextLen = snprintf( _strTextBuff, 1024, "<block id=\"%d\"coordx=\"%d\"coordy=\"%d\">\n",
						  _pBlock->m_nID, _pBlock->m_nCoordX, _pBlock->m_nCoordY );
			pFileWrite->AddText( _strTextBuff, _nTextLen );
            for( k = 0; k < SUBBLOCKLAYERSIZE; k ++ ){
                if( _pBlock->m_sLayerInfo.m_sLayerInfo.m_vTerrainBlockLayer[k] ){
                    const char* _strTextureFileName = _pTerrainTextureList->GetTextureName( _pBlock->m_sLayerInfo.m_sLayerInfo.m_vTerrainBlockLayer[k] );
			        _nTextLen = snprintf( _strTextBuff, 1024, "<layer texturefile=\"%s\"uvtype=\"%d\"/>\n",
						          _strTextureFileName, _pBlock->m_sLayerInfo.m_sLayerInfo.m_vUVType[k] );
			        pFileWrite->AddText( _strTextBuff, _nTextLen );
                }else{
                    break;
                }
            }
			_nTextLen = snprintf( _strTextBuff, 1024, "<alphadata>" );
			pFileWrite->AddText( _strTextBuff, _nTextLen );
            uint32* _pAlphaData = _pBlock->m_sLayerInfo.m_sLayerInfo.m_vTerrainBlockLayerAlphaData;
            for( k =0; k < SUBBLOCKGRIDCOUNT * SUBBLOCKGRIDCOUNT; k ++, _pAlphaData ++ ){
                if( k == SUBBLOCKGRIDCOUNT * SUBBLOCKGRIDCOUNT - 1 ){
                    _nTextLen = snprintf( _strTextBuff, 1024, "%08x", *_pAlphaData );
                }else{
			        _nTextLen = snprintf( _strTextBuff, 1024, "%08x,", *_pAlphaData );
                }
			    pFileWrite->AddText( _strTextBuff, _nTextLen );
            }
            _nTextLen = snprintf( _strTextBuff, 1024, "</alphadata>\n" );
			pFileWrite->AddText( _strTextBuff, _nTextLen );

            _nTextLen = snprintf( _strTextBuff, 1024, "</block>\n" );
            pFileWrite->AddText( _strTextBuff, _nTextLen );
		}
	}
    _nTextLen = snprintf( _strTextBuff, 1024, "</blocklist>\n" );
    pFileWrite->AddText( _strTextBuff, _nTextLen );

    //height map
	_nTextLen = snprintf( _strTextBuff, 1024, "<heightdata>" );
	pFileWrite->AddText( _strTextBuff, _nTextLen );
    float* _pHeightData = m_pHeightMap;
    for( k =0; k < m_nCellCountX * m_nCellCountY; k ++, _pHeightData ++ ){
        if( k == m_nCellCountX * m_nCellCountY - 1 ){
            _nTextLen = snprintf( _strTextBuff, 1024, "%.4f", *_pHeightData );
        }else{
			_nTextLen = snprintf( _strTextBuff, 1024, "%.4f,", *_pHeightData );
        }
		pFileWrite->AddText( _strTextBuff, _nTextLen );
    }
    _nTextLen = snprintf( _strTextBuff, 1024, "</heightdata>\n" );
	pFileWrite->AddText( _strTextBuff, _nTextLen );

    //color map
	_nTextLen = snprintf( _strTextBuff, 1024, "<colordata>" );
	pFileWrite->AddText( _strTextBuff, _nTextLen );
    color32* _pColorMap = m_pColorMap;
    for( k =0; k < m_nCellCountX * m_nCellCountY; k ++, _pColorMap ++ ){
        if( k == m_nCellCountX * m_nCellCountY - 1 ){
            _nTextLen = snprintf( _strTextBuff, 1024, "%08x", *_pColorMap );
        }else{
			_nTextLen = snprintf( _strTextBuff, 1024, "%08x,", *_pColorMap );
        }
		pFileWrite->AddText( _strTextBuff, _nTextLen );
    }
    _nTextLen = snprintf( _strTextBuff, 1024, "</colordata>\n" );
	pFileWrite->AddText( _strTextBuff, _nTextLen );

    //end
    _nTextLen = snprintf( _strTextBuff, 1024, "</terrain>\n" );
    pFileWrite->AddText( _strTextBuff, _nTextLen );
    return True;
}
bool32 CTerrainObject::Open( const char* strResourceSpace, CXML* pXML, HXMLNODEHANDLE hSceneHandle ){
    HXMLNODEHANDLE _hTerrainNode = pXML->GetNode( hSceneHandle, "terrain");

    m_rTerrainTextureList = NEW CTerrainTextureList();
    m_rTerrainTextureList->Init( strResourceSpace );

    m_nBlockCountX = pXML->GetAttributeValueInt32( _hTerrainNode, "blockcountx" );
    m_nBlockCountY = pXML->GetAttributeValueInt32( _hTerrainNode, "blockcounty" );
    m_fBlockSize = pXML->GetAttributeValueFloat( _hTerrainNode, "blocksize" );

    float _fStartPosX = -(m_fBlockSize * (m_nBlockCountX) * 0.5f );
    float _fStartPosY = -(m_fBlockSize * (m_nBlockCountY) * 0.5f );

    m_fStartPosX = _fStartPosX;
    m_fStartPosY = _fStartPosY;

    int32 _nSizeBlockX = m_nBlockCountX;
    int32 _nSizeBlockY = m_nBlockCountY;

    m_fCellSize = m_fBlockSize / (SUBBLOCKGRIDCOUNT - 1);
    m_fHalfCellSize = m_fCellSize * 0.5f;

    m_nCellCountX = (_nSizeBlockX * (SUBBLOCKGRIDCOUNT - 1) + 1);
    m_nCellCountY = (_nSizeBlockY * (SUBBLOCKGRIDCOUNT - 1) + 1);
    // height map
    m_pHeightMap = (float*)MALLOC( m_nCellCountX * m_nCellCountY * sizeof( float ) );
    float* _pHeightMap = m_pHeightMap;
    HXMLNODEHANDLE _hTmpNode = pXML->GetNode( _hTerrainNode, "heightdata");
    int32 _nValueSize = pXML->GetValueSize( _hTmpNode );
    int32 _nCount = 0;
    char* _strTmpValue = (char*)MALLOC( _nValueSize + 100 );
    char* _strTmp0 = (char*)pXML->GetValue( _hTmpNode, _strTmpValue );
    char* _strTmp1 = strtok( _strTmp0, ",");
    while( _strTmp1 ){
        *_pHeightMap = StrToFloat( _strTmp1 );
        _pHeightMap ++;
        _nCount++;
        _strTmp1 = strtok( NULL, "," );
    }
    FREE( _strTmpValue );
    assert( _nCount == m_nCellCountX * m_nCellCountY  );

    //color map
    m_pColorMap =  (color32*)MALLOC( m_nCellCountX * m_nCellCountY * sizeof( color32 ) );
    color32* _pColorMap = m_pColorMap;
    _hTmpNode = pXML->GetNode( _hTerrainNode, "colordata");
    _nValueSize = pXML->GetValueSize( _hTmpNode );
    _nCount = 0;
    _strTmpValue = (char*)MALLOC( _nValueSize + 100 );
    _strTmp0 = (char*)pXML->GetValue( _hTmpNode, _strTmpValue );
    _strTmp1 = strtok( _strTmp0, ",");
    while( _strTmp1 ){
        *_pColorMap = HexstrToUint32( _strTmp1 );
        _pColorMap ++;
        _nCount++;
        _strTmp1 = strtok( NULL, "," );
    }
    FREE( _strTmpValue );
    assert( _nCount == m_nCellCountX * m_nCellCountY  );


    m_rRenderItem = NEW CRenderItem( m_vWorldM );
    m_rRenderItem->m_eRenderType = 0;
    m_rRenderItem->m_bCastShadow = False;
    m_rRenderItem->m_bReceiveShadow = True;
    m_rRenderItem->m_bFogEnable = True;
    m_rRenderItem->m_bLightEnable = True;
    m_rRenderItem->m_bLineMode = False;

    CIndexBuffer* _pTBIB = m_rTerrainBlockIndexBuffer.GetEntityData();

    CRenderElement* _pRenderElement = m_rRenderItem->m_pRenderElement.GetEntityData();
    NEW2DARRAY( m_ppvTerrainBlockList, CTerrainBlock*, _nSizeBlockX, _nSizeBlockY );
    //CTexture* _pTextureObj = m_rTerrainTextureList->GetTextureFile( "default.jpg" );

    CTerrainTextureList* _pTerrainTextureList = m_rTerrainTextureList.GetEntityData();

    _hTmpNode = pXML->GetNode( _hTerrainNode, "blocklist");
    assert( pXML->GetChildCount( _hTmpNode ) == _nSizeBlockY * _nSizeBlockX );

    _hTmpNode = pXML->GetNode( _hTmpNode, "block" );
    int32 _nBlockID = 0;
    for (int32 j = 0; j < _nSizeBlockY; j ++) {
        for(int32 i = 0; i < _nSizeBlockX; i++) {
            float _fPosX = _fStartPosX + i * m_fBlockSize;
            float _fPosY = _fStartPosY + j * m_fBlockSize;
            CTerrainBlock* _pTerrainBlock = NEW CTerrainBlock( _nBlockID ++, _nSizeBlockX, _nSizeBlockY, i, j, _fPosX, _fPosY, m_fBlockSize, NULL );
            _pTerrainBlock->Open( m_pHeightMap, m_pColorMap, _pRenderElement, _pTBIB, _pTerrainTextureList, pXML, _hTmpNode );
            m_ppvTerrainBlockList[j][i] = _pTerrainBlock;
            m_vTerrainBlockPool.PushBack(_pTerrainBlock);

            _hTmpNode = pXML->GetNextNode( _hTmpNode );
        }
    }
    return True;
}